package com.example.oss.service;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.oss.entity.*;
import com.example.oss.mapper.CourseMapper;
import com.example.oss.util.RedisKeyUtil;
import com.example.oss.util.ThreadPoolUtil;
import com.example.oss.util.UserContextHolder;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class CourseService {

    @Resource
    private CourseMapper courseMapper;
    @Autowired
    private RedisTemplate redisTemplate;
    @Resource
    private DefaultRedisScript lockAmountRedisScript;
    //    @Autowired
//    private MessageSender messageSender;
    @Autowired
    private OrderService orderService;

//    private static Map<Integer, Boolean> map = new HashMap<>();

    //本地缓存-全部课程列表
    private LoadingCache<String, List<Course>> allCourseListCache = CacheBuilder
            .newBuilder()
            .initialCapacity(100)
            .expireAfterWrite(61, TimeUnit.SECONDS)
            .build(new CacheLoader<String, List<Course>>() {
                @Override
                public List<Course> load(String s) throws Exception {
                    return courseMapper.selectList(null);
                }
            });
    //本地缓存-可选课程列表
    private LoadingCache<String, List<Course>> ableCourseListCache = CacheBuilder
            .newBuilder()
            .initialCapacity(100)
            .expireAfterWrite(61, TimeUnit.SECONDS)
            .build(new CacheLoader<String, List<Course>>() {
                @Override
                public List<Course> load(String s) throws Exception {
                    return courseMapper.selectList(new QueryWrapper<Course>().gt("amount", 0));
                }
            });

    /**
     * 初始化操作
     */
    @PostConstruct
    private void init() {

        //初始化数据库中的课程余额到Redis
        List<Course> allCourses = courseMapper.selectList(null);
        if (allCourses != null) {
            allCourses.forEach(e -> {
                String courseAmountKey = RedisKeyUtil.getCourseAmountKey(e.getId());
                redisTemplate.opsForValue().set(courseAmountKey, e.getAmount());
//                map.put(e.getId(), false); //map标记每个课程余额是否为空
            });

        }

        //开启定时任务,每隔一分钟把DB中的课程信息刷新到redis中
        ThreadPoolUtil.scheduledThreadPool.scheduleWithFixedDelay(() -> {

            String courseListAKey = RedisKeyUtil.getCourseListAbleAKey();
            String courseListBKey = RedisKeyUtil.getCourseListAbleBKey();
            //查询全部可选课程
            List<Course> courses = courseMapper.selectList(new QueryWrapper<Course>().ge("amount", 0));

            //加载到redis中，使用list结构存储（先刷新B，再刷新A）
            redisTemplate.delete(courseListBKey);
            redisTemplate.opsForList().leftPushAll(courseListBKey, courses);

            redisTemplate.delete(courseListAKey);
            redisTemplate.opsForList().leftPushAll(courseListAKey, courses);

            log.info("可选课程列表定时刷新完成.........");
        }, 0, 60, TimeUnit.SECONDS);

        ThreadPoolUtil.scheduledThreadPool.scheduleWithFixedDelay(() -> {

            String courseListAKey = RedisKeyUtil.getCourseListAllAKey();
            String courseListBKey = RedisKeyUtil.getCourseListAllBKey();
            //查询全部课程
            List<Course> courses = courseMapper.selectList(null);

            //加载到redis中，使用list结构存储（先刷新B，再刷新A）
            redisTemplate.delete(courseListBKey);
            redisTemplate.opsForList().leftPushAll(courseListBKey, courses);

            redisTemplate.delete(courseListAKey);
            redisTemplate.opsForList().leftPushAll(courseListAKey, courses);

            log.info("全部课程列表定时刷新完成.........");
        }, 0, 60, TimeUnit.SECONDS);
    }

    //-------------------------------------选课-----------------------------------------

    public Result doSelectFromDB(int courseId, int stuId) {
        if (orderService.existStuId(stuId)) {
            return Result.failure(ResultEnum.SELECT_REPEAT);
        }
        if (getCourseByIdFromDB(courseId).getAmount() < 1) {
            return Result.failure(ResultEnum.SELECT_EMPTY);
        }

        Order order = orderService.order(courseId, stuId);
        if (order == null) {
            return Result.failure(ResultEnum.SELECT_FAILURE);
        } else {
            return Result.success(ResultEnum.SELECT_SUCCESS);
        }
    }



    /**
     * 选课
     *
     * @param courseId 课程id
     * @param stuId    学生id（用户id）
     * @return
     */
    public Result doSelect(int courseId, int stuId) {
        //本地map标记验空
//        Boolean isEmpty = map.get(courseId);
//        if (isEmpty) {
//            return Result.response(ResultEnum.EMPTY);
//        }
        //redis验重
        String orderKey = RedisKeyUtil.getOrderKey(stuId);
        if (redisTemplate.hasKey(orderKey)) {
            return Result.failure(ResultEnum.SELECT_REPEAT);
        }

        //redis减库存, 使用lua脚本实现分布式锁，保证amount数据安全
        String courseAmountKey = RedisKeyUtil.getCourseAmountKey(courseId);
        Long amount = (Long) redisTemplate.execute(lockAmountRedisScript, Arrays.asList(courseAmountKey), Collections.EMPTY_LIST);
        //如果小于0，说明库存为空, 直接返回
        if (amount < 0) {
//            map.put(courseId, true);
            redisTemplate.opsForValue().increment(courseAmountKey);
            return Result.failure(ResultEnum.SELECT_EMPTY);
        }
        //入队
        Message message = new Message();
        message.setRoutingKey("placeAnOrder");
        message.setCourseId(courseId);
        message.setStuId(stuId);

//        messageSender.send(message);
        //返回排队中
        return Result.success(ResultEnum.SELECT_QUEUING);
    }


    public Result doSelectTest(int courseId, int stuId) {
        //redis验重
        String orderKey = RedisKeyUtil.getOrderKey(stuId);
        if (redisTemplate.hasKey(orderKey)) {
            return Result.failure(ResultEnum.SELECT_REPEAT);
        }
        //redis减库存, 使用lua脚本实现分布式锁，保证amount数据安全
        String courseAmountKey = RedisKeyUtil.getCourseAmountKey(courseId);
        Long amount = (Long) redisTemplate.execute(lockAmountRedisScript, Arrays.asList(courseAmountKey), Collections.EMPTY_LIST);
        //如果小于0，说明库存为空, 直接返回
        if (amount < 0) {
            redisTemplate.opsForValue().increment(courseAmountKey);
            return Result.failure(ResultEnum.SELECT_EMPTY);
        } else {
            //DB验重
            if (orderService.existStuId(stuId)) {
                return Result.failure(ResultEnum.SELECT_REPEAT);
            }
            //DB验库存
            if (getCourseByIdFromDB(courseId).getAmount() < 1) {
                return Result.failure(ResultEnum.SELECT_EMPTY);
            }
            //下单
            Order order = orderService.order(courseId, stuId);
            if (order != null) {
                ThreadPoolUtil.executorService.execute(() -> {
                    //DB下单成功，异构订单信息到缓存中，以提供我的订单中查询
                    try {
                        Course course = getCourseByIdFromDB(order.getCourseId());
                        Map map = JSON.parseObject(JSON.toJSONString(course), Map.class);
                        redisTemplate.opsForHash().putAll(RedisKeyUtil.getOrderKey(stuId), map);
                    } catch (Exception e) {
                        e.printStackTrace();
                        log.error("订单信息异构到缓存失败！");
                    }
                });
                return Result.success(ResultEnum.SELECT_SUCCESS);
            } else {
                return Result.failure(ResultEnum.SELECT_FAILURE);
            }
        }
    }


    //--------------------------------------轮询获取选课结果--------------------------------------

    public Result getSelectResult(int courseId, int stuId) {
        //检查DB是否有订单
        Order order = orderService.getOrderByStuIdFromDB(stuId);
        if (order != null) {
            //有订单代表选课成功
            return Result.success(ResultEnum.SELECT_SUCCESS);
        } else {
            //看所选课程是否还有余额，如果没有，返回失败课程余额为空，否则返回排队中
            if (getCourseByIdFromDB(courseId).getAmount() < 1) {
                return Result.failure(ResultEnum.SELECT_EMPTY);
            }
            return Result.success(ResultEnum.SELECT_QUEUING);
        }
    }

    /**
     * 获取我的选课信息
     *
     * @return
     */
    public Course getMySelectedCourseInfo() {
        Student student = UserContextHolder.getUser();
        String orderKey = RedisKeyUtil.getOrderKey(student.getId());

        Map entries = redisTemplate.opsForHash().entries(orderKey);
        Course course = JSON.parseObject(JSON.toJSONString(entries), Course.class);
        if (course == null) {
            //走DB
            Order order = orderService.getOrderByStuIdFromDB(student.getId());
            course = courseMapper.selectById(order.getCourseId());
        }
        return course;
    }

    //---------------------------------选课列表--------------------------------------------

    /**
     * 从DB中获取全部可选课程
     *
     * @return
     */
    public CourseResponse getAbleCourseListFromDB(int page, int limit) {
        CourseResponse courseResponse = new CourseResponse();

        QueryWrapper<Course> queryWrapper = new QueryWrapper<>();
        queryWrapper.gt("amount", 0);
        com.baomidou.mybatisplus.extension.plugins.pagination.Page<Course> coursePage =
                courseMapper.selectPage(new Page<>(page, limit), queryWrapper);
        Integer totalSize = courseMapper.selectCount(queryWrapper);

        courseResponse.setCourseList(coursePage.getRecords());
        courseResponse.setTotalSize(totalSize);
        return courseResponse;
    }

    /**
     * 从redis中获取可选课程列表
     */
    public CourseResponse getAbleCourseList(int page, int limit) {
        CourseResponse courseResponse = new CourseResponse();
        //计算分页参数
        int start = page * limit - limit;
        int end = start + limit - 1;
        String courseListAKey = RedisKeyUtil.getCourseListAbleAKey();
        String courseListBKey = RedisKeyUtil.getCourseListAbleBKey();
        //先从A缓存中读
        List<Course> courseList = redisTemplate.opsForList().range(courseListAKey, start, end);
        Integer totalSize = redisTemplate.opsForList().size(courseListAKey).intValue();

        //如果A缓存为空，走B缓存
        if (CollectionUtils.isEmpty(courseList)) {
            //走B缓存查询
            courseList = redisTemplate.opsForList().range(courseListBKey, start, end);
            totalSize = redisTemplate.opsForList().size(courseListBKey).intValue();
            //如果B缓存也为空，走本地缓存查询
            if (CollectionUtils.isEmpty(courseList)) {
                //本地缓存
                courseResponse = getAbleCourseListFromLocalCache(page, limit);
                if (courseResponse != null) {
                    return courseResponse;
                } else {
                    //走DB查询
                    return getAbleCourseListFromDB(page, limit);
                }
            }
        }
        courseResponse.setCourseList(courseList);
        courseResponse.setTotalSize(totalSize);
        return courseResponse;
    }

    /**
     * 从redis中获取全部课程列表
     *
     * @param page
     * @param limit
     * @return
     */
    public CourseResponse getAllCourseList(Integer page, Integer limit) {
        CourseResponse courseResponse = new CourseResponse();
        //计算分页参数
        int start = page * limit - limit;
        int end = start + limit - 1;
        String courseListAllAKey = RedisKeyUtil.getCourseListAllAKey();
        String courseListAllBKey = RedisKeyUtil.getCourseListAllBKey();
        //先从A缓存中读
        List<Course> courseList = redisTemplate.opsForList().range(courseListAllAKey, start, end);
        Integer totalSize = redisTemplate.opsForList().size(courseListAllAKey).intValue();

        //如果A缓存为空，走B缓存
        if (CollectionUtils.isEmpty(courseList)) {
            //走B缓存查询
            courseList = redisTemplate.opsForList().range(courseListAllBKey, start, end);
            totalSize = redisTemplate.opsForList().size(courseListAllBKey).intValue();
            //如果B缓存也为空，走DB查询
            if (CollectionUtils.isEmpty(courseList)) {
                //本地缓存
                courseResponse = getAllCourseListFromLocalCache(page, limit);
                if (courseResponse != null) {
                    return courseResponse;
                } else {
                    //走DB查询
                    return getAllCourseListFromDB(page, limit);
                }
            }
        }
        courseResponse.setCourseList(courseList);
        courseResponse.setTotalSize(totalSize);
        return courseResponse;
    }

    /**
     * 从DB中获取全部课程列表
     *
     * @param page
     * @param limit
     * @return
     */
    public CourseResponse getAllCourseListFromDB(Integer page, Integer limit) {
        CourseResponse courseResponse = new CourseResponse();

        com.baomidou.mybatisplus.extension.plugins.pagination.Page<Course> coursePage =
                courseMapper.selectPage(new Page<Course>(page, limit), null);
        Integer totalSize = courseMapper.selectCount(null);

        courseResponse.setCourseList(coursePage.getRecords());
        courseResponse.setTotalSize(totalSize);
        return courseResponse;

    }

    /**
     * 从本地缓存获取全部课程列表
     *
     * @param page
     * @param limit
     * @return
     */
    public CourseResponse getAllCourseListFromLocalCache(Integer page, Integer limit) {
        CourseResponse courseResponse = new CourseResponse();
        List<Course> courseList = null;
        try {
            courseList = allCourseListCache.get("allCourseList");
        } catch (ExecutionException e) {
            e.printStackTrace();
            log.info("本地缓存出现异常！");
            return null;
        }
        if (CollectionUtils.isEmpty(courseList)) {
            return null;
        }
        int totalSize = courseList.size();
        //分页
        int start = page * limit - limit;
        int end = start + limit - 1;
        List<Course> courses = courseList.subList(start, end);

        courseResponse.setCourseList(courses);
        courseResponse.setTotalSize(totalSize);
        return courseResponse;

    }


    /**
     * 从本地缓存获取可选课程列表
     *
     * @param page
     * @param limit
     * @return
     */
    public CourseResponse getAbleCourseListFromLocalCache(Integer page, Integer limit) {
        CourseResponse courseResponse = new CourseResponse();
        List<Course> courseList = null;
        try {
            courseList = ableCourseListCache.get("ableCourseList");
        } catch (ExecutionException e) {
            e.printStackTrace();
            log.info("本地缓存出现异常！");
            return null;
        }
        if (CollectionUtils.isEmpty(courseList)) {
            return null;
        }
        int totalSize = courseList.size();
        //分页
        int start = page * limit - limit;
        int end = start + limit - 1;
        List<Course> courses = courseList.subList(start, end);

        courseResponse.setCourseList(courses);
        courseResponse.setTotalSize(totalSize);
        return courseResponse;

    }


    /**
     * 退课
     *
     * @param courseId
     * @param stuId
     */
    @Transactional
    public void dropCourse(int courseId, int stuId) {
        orderService.deleteOrderByStuId(stuId);
        courseMapper.increaseAmount(courseId);

        String courseAmountKey = RedisKeyUtil.getCourseAmountKey(courseId);
        String orderKey = RedisKeyUtil.getOrderKey(stuId);
        redisTemplate.delete(orderKey);
        redisTemplate.opsForValue().increment(courseAmountKey);
    }


    public Course getCourseByIdFromDB(int courseId) {
        return courseMapper.selectById(courseId);
    }


}

